<template>
	<div ref="focusAnimtShell" :class="classShell" :style="styleShell">
		<slot></slot>
	</div>
</template>

<script>
	// nvue模式下引入动画模块
	// #ifdef APP-NVUE
	const animation = weex.requireModule('animation');
	// #endif
	
	/**
	 * 动画执行容器
	 * @description vue/nvue通用，动画执行容器，提供一些常用的元素动画，动画速度曲线统一为linear
	 * @tutorial
	 * @property {String} tag 组件自定义标识，默认：focus-animation
	 * @property {String} type = [opacity__0_1|opacity__1_0|opacity__1_0_1|rotate__0_360|scale__o5_1|scale__1_o5|scale__1_o8_1] 动画执行类型
	 *   @value opacity__0_1 透明度类：透明度0→1，渐入效果
	 *   @value opacity__1_0 透明度类：透明度1→0，渐出效果
	 *   @value opacity__1_0_1 透明度类：透明度1→0→1效果，闪烁效果
	 *   @value rotate__0_360 旋转类：顺时针旋转360°，可用于数据加载中提示
	 *   @value scale__o5_1 缩放类：缩放倍数0.5→1，入场效果
	 *   @value scale__1_o5 缩放类：缩放倍数1→o5，离场效果
	 *   @value scale__1_o8_1 缩放类：缩放倍数1→0.8→1，点击效果
	 * @property {String,Number} duration 动画一个周期的持续时间(ms)，默认：500
	 * @property {String} count = [1|2|infinite] 动画执行次数，可自行填写，默认：1
	 *   @value 1 只执行一次
	 *   @value 2 执行两次
	 *   @value infinite 无限循环
	 * @event {Function} finish 动画执行结束时触发，注：count=infinite时永不触发
	 */
	export default {
		name: "focus-animation",
		// 传递数据
		props: {
			"tag": { type: String, default: 'focus-animation' },
			"type": { type: String, default: '' },
			"duration": { type: [String,Number], default: 500 },
			"count": { type: [String,Number], default: 1 },
		},
		
		// 页面数据
		data() {
			return {
				// 定时器对象
				timer: {
					shellDomFind: null, // 外层容器dom渲染，定时器
					animationEnd: null, // .vue进行动画完成倒计时
				},
				// 外框class属性，用于添加动画css类
				classShell: '',
				// 当前动画执行次数
				num_animt_complete: 0,
			}
		},
		
		// 计算属性
		computed: {
			// 外层style属性
			styleShell() {
				// 初始化
				var obj_style = {};
				var str_style = '';
				
				// vue模式下才使用css3动画样式的设置
				// #ifndef APP-NVUE
					
					// ====================================== 动画一个周期的持续时间(ms)
					// 传入数据大于0执行
					if (Number(this.duration) >= 0)
					{
						obj_style['animation-duration'] = this.duration + 'ms';
					}
					// 其它情况都等于500
					else {
						obj_style['animation-duration'] = '500ms';
					}
					
					// ====================================== 动画执行次数
					// 如果传入的 'infinite'
					if (this.count == 'infinite')
					{
						obj_style['animation-iteration-count'] = 'infinite';
					}
					// 传入数据大于0执行
					else if (Number(this.count) > 0)
					{
						obj_style['animation-iteration-count'] = this.count;
					}
					// 其它情况都等于1
					else {
						obj_style['animation-iteration-count'] = 1;
					}
					
				// #endif
				
				// 循环样式对象
				for (let k in obj_style) { str_style += k + ':' + obj_style[k] + ';'; }
				// 结果返回
				return str_style;
			}
		},
		
		// 数据监听
		watch: {
			// 动画执行类型
			type: {
				handler(newValue, oldValue) {
					// 如果类型数据有效
					if (newValue.length > 0)
					{
						// 先将动画任务次数初始化
						this.num_animt_complete = 0;
						
						// vue模式下使用css
						// #ifndef APP-NVUE
							// 动画css样式赋值
							this.classShell = '_focus-animation-class-' + newValue;
							// 动画执行完毕倒计时
							this.vueWorkEnd();
						// #endif
						
						// nvue模式下使用weex动画模块
						// #ifdef APP-NVUE
							// 等待外层dom渲染完毕
							this.domShellDraw(()=>{
								// 透明度类
								if (newValue == 'opacity__0_1') { this.run_opacity__0_1(); }
								else if (newValue == 'opacity__1_0') { this.run_opacity__1_0(); }
								else if (newValue == 'opacity__1_0_1') { this.run_opacity__1_0_1(); }
								// 旋转类
								else if (newValue == 'rotate__0_360') { this.run_rotate__0_360(); }
								// 缩放类
								else if (newValue == 'scale__o5_1') { this.run_scale__o5_1(); }
								else if (newValue == 'scale__1_o5') { this.run_scale__1_o5(); }
								else if (newValue == 'scale__1_o8_1') { this.run_scale__1_o8_1(); }
							});
						// #endif
					}
				},
				immediate: true
			},
		},
		
		// 组件销毁
		beforeDestroy() {
			// 注销定时器
			if (this.timer.shellDomFind) { clearTimeout(this.timer.shellDomFind); }
			if (this.timer.animationEnd) { clearTimeout(this.timer.animationEnd); }
		},
		
		// 页面事件
		methods: {
			
			// ====================================== 数据处理
			// 外层dom是否渲染完毕
			domShellDraw(callback) {
				// 先销毁一次定时器
				if (this.timer.shellDomFind) { clearTimeout(this.timer.shellDomFind); }
				// 延时寻找dom元素
				this.timer.shellDomFind = setTimeout(()=>{
					// 如果找到了dom元素
					if (this.$refs.focusAnimtShell) { if (callback) { callback(); } }
					// 没找到回调自身
					else { this.domShellDraw(callback); }
				}, 200);
			},
			
			// 通用动画执行方法，参数：styles-动画描述，duration-动画时间ms
			animationMakeWay(styles, duration) {
				// 异步方法标识
				return new Promise((resolve) => {
					// 获取外层dom元素
					var dom_animt = this.$refs.focusAnimtShell;
					// weex动画方法
					animation.transition(dom_animt, {
						styles: styles, // 过渡效果的键值对对象
						duration: Number(duration), // 动画时间ms
						delay: 0, // 延迟执行时间
						needLayout: false, // 动画执行是否影响布局
						timingFunction: 'linear', // 动画执行的速度曲线
					}, () => {
						// 调用异步执行"结束标记"
						resolve();
					});
				});
			},
			
			// .nvue动画次数任务是否完成，参数：unfinished-未完成回调函数
			isWorkFinish(unfinished) {
				// 参数有效判定
				if (!unfinished) { unfinished = () => {}; }
				// 如果是设置的无限循环，那肯定未完成
				if (this.count == 'infinite') { unfinished(); }
				// 否则记录次数
				else {
					// 动画完成次数+1
					this.num_animt_complete++;
					// 如果已经完成任务
					if (this.num_animt_complete >= Number(this.count)) {
						// 动画执行完毕回调
						this.$emit('finish', { "tag": this.tag, "type": this.type });
					}
					// 否则递归自身
					else { unfinished(); }
				}
			},
			
			// .vue动画执行完毕倒计时处理
			vueWorkEnd() {
				// 动画执行次数转换
				var num_work = 1;
				// 如果传入的 'infinite'
				if (this.count == 'infinite') { num_work = null; }
				// 传入数据大于0执行
				else if (Number(this.count) > 0) { num_work = Number(this.count); }
				// 如果次数有效执行
				if (num_work >= 1)
				{
					// 计算动画的总时间
					var time_lang = 500; // 单位：ms
					// 如果传入时长数据大于0
					if (Number(this.duration) >= 0) { time_lang = Number(this.duration); }
					// 先销毁一次定时器
					if (this.timer.animationEnd) { clearTimeout(this.timer.animationEnd); }
					// 动画执行倒计时
					this.timer.animationEnd = setTimeout(()=>{
						// 动画执行完毕回调
						this.$emit('finish', { "tag": this.tag, "type": this.type });
					}, time_lang * num_work);
				}
			},
			
			
			// ====================================== nvue动画方法
			// 透明度类：透明度 0→1 渐入效果
			async run_opacity__0_1() {
				
				// 1. 透明度瞬间 变为0 (初始状态)
				await this.animationMakeWay({ opacity: 0 }, 0);
				// 2. 透明度逐渐 变为1
				await this.animationMakeWay({ opacity: 1 }, this.duration);
				
				// 判断是否完成次数要求任务
				this.isWorkFinish(()=>{
					// 还未完成，递归自身
					this.run_opacity__0_1();
				});
			},
			// 透明度类：透明度 1→0 渐出效果
			async run_opacity__1_0() {
				
				// 1. 透明度瞬间 变为1 (初始状态)
				await this.animationMakeWay({ opacity: 1 }, 0);
				// 2. 透明度逐渐 变为0
				await this.animationMakeWay({ opacity: 0 }, this.duration);
				
				// 判断是否完成次数要求任务
				this.isWorkFinish(()=>{
					// 还未完成，递归自身
					this.run_opacity__1_0();
				});
			},
			// 透明度类：透明度 0→1→0 闪烁效果
			async run_opacity__1_0_1() {
				
				// 因为动画分两步，所以时间各取一半
				var time_step = Number(this.duration) / 2;
				
				// 1. 透明度瞬间 变为1 (初始状态)
				await this.animationMakeWay({ opacity: 1 }, 0);
				// 2. 透明度逐渐 变为0
				await this.animationMakeWay({ opacity: 0 }, time_step);
				// 3. 透明度逐渐 变为1
				await this.animationMakeWay({ opacity: 1 }, time_step);
				
				// 判断是否完成次数要求任务
				this.isWorkFinish(()=>{
					// 还未完成，递归自身
					this.run_opacity__1_0_1();
				});
			},
			
			// 旋转类：顺时针旋转360° 等待中效果
			async run_rotate__0_360() {
				
				// 1. 旋转角度逐渐 变为360°
				await this.animationMakeWay({
					transform: 'rotate(360deg)',
					transformOrigin: 'center center',
				}, this.duration);
				// 2. 旋转角度瞬间 变为0° (初始状态)
				await this.animationMakeWay({
					transform: 'rotate(0deg)',
					transformOrigin: 'center center',
				}, 0);
				
				// 判断是否完成次数要求任务
				this.isWorkFinish(()=>{
					// 还未完成，递归自身
					this.run_rotate__0_360();
				});
			},
			
			// 缩放类：缩放倍数 0.5→1 入场效果
			async run_scale__o5_1() {
				
				// 1. 缩放倍数瞬间 变为0.5 (初始状态)
				await this.animationMakeWay({
					transform: 'scale(0.5)',
					transformOrigin: 'center center',
				}, 0);
				// 2. 缩放倍数逐渐 变为1
				await this.animationMakeWay({
					transform: 'scale(1)',
					transformOrigin: 'center center',
				}, this.duration);
				
				// 判断是否完成次数要求任务
				this.isWorkFinish(()=>{
					// 还未完成，递归自身
					this.run_scale__o5_1();
				});
			},
			// 缩放类：缩放度 1→0.5 离场效果
			async run_scale__1_o5() {
				
				// 1. 缩放倍数瞬间 变为1 (初始状态)
				await this.animationMakeWay({
					transform: 'scale(1)',
					transformOrigin: 'center center',
				}, 0);
				// 2. 缩放倍数逐渐 变为0.5
				await this.animationMakeWay({
					transform: 'scale(0.5)',
					transformOrigin: 'center center',
				}, this.duration);
				
				// 判断是否完成次数要求任务
				this.isWorkFinish(()=>{
					// 还未完成，递归自身
					this.run_scale__1_o5();
				});
			},
			// 缩放类：缩放度 1→0.8→1 点击效果
			async run_scale__1_o8_1() {
				
				// 因为动画分两步，所以时间各取一半
				var time_step = Number(this.duration) / 2;
				
				// 1. 缩放倍数瞬间 变为1 (初始状态)
				await this.animationMakeWay({
					transform: 'scale(1)',
					transformOrigin: 'center center',
				}, 0);
				// 2. 缩放倍数逐渐 变为0.8
				await this.animationMakeWay({
					transform: 'scale(0.8)',
					transformOrigin: 'center center',
				}, time_step);
				// 3. 缩放倍数逐渐 变为1
				await this.animationMakeWay({
					transform: 'scale(1)',
					transformOrigin: 'center center',
				}, time_step);
				
				// 判断是否完成次数要求任务
				this.isWorkFinish(()=>{
					// 还未完成，递归自身
					this.run_scale__1_o8_1();
				});
			},
			
		},
		
	}
</script>

<style scoped>
	/* vue模式下使用css3动画 */
	/* #ifndef APP-NVUE */
	
		/* 透明度类：透明度0→1，渐入效果 */
		@keyframes _focus_animation_keyframes_opacity__0_1{
			0%{ opacity: 0; }
			100%{ opacity: 1; }
		}
		._focus-animation-class-opacity__0_1{
			animation-name: _focus_animation_keyframes_opacity__0_1;
			animation-timing-function: linear;
			animation-fill-mode: both;
		}
		/* 透明度类：透明度1→0，渐出效果 */
		@keyframes _focus_animation_keyframes_opacity__1_0{
			0%{ opacity: 1; }
			100%{ opacity: 0; }
		}
		._focus-animation-class-opacity__1_0{
			animation-name: _focus_animation_keyframes_opacity__1_0;
			animation-timing-function: linear;
			animation-fill-mode: both;
		}
		/* 透明度类：透明度1→0→1，闪烁效果 */
		@keyframes _focus_animation_keyframes_opacity__1_0_1{
			0%{ opacity: 1; }
			50%{ opacity: 0; }
			100%{ opacity: 1; }
		}
		._focus-animation-class-opacity__1_0_1{
			animation-name: _focus_animation_keyframes_opacity__1_0_1;
			animation-timing-function: linear;
			animation-fill-mode: both;
		}
		
		/* 旋转类：以元素中心点顺时针360°旋转，可用于数据加载提示 */
		@keyframes _focus_animation_keyframes_rotate__0_360{
			0%{ transform: rotate(0); }
			100%{ transform: rotate(360deg); }
		}
		._focus-animation-class-rotate__0_360{
			animation-name: _focus_animation_keyframes_rotate__0_360;
			animation-timing-function: linear;
			animation-fill-mode: both;
		}
		
		/* 缩放类：缩放倍数0.5→1，入场效果 */
		@keyframes _focus_animation_keyframes_scale__o5_1{
			0%{ transform: scale(0.5); }
			100%{ transform: scale(1); }
		}
		._focus-animation-class-scale__o5_1{
			animation-name: _focus_animation_keyframes_scale__o5_1;
			animation-timing-function: linear;
			animation-fill-mode: both;
		}
		/* 缩放类：缩放倍数1→0.5，离场效果 */
		@keyframes _focus_animation_keyframes_scale__1_o5{
			0%{ transform: scale(1); }
			100%{ transform: scale(0.5); }
		}
		._focus-animation-class-scale__1_o5{
			animation-name: _focus_animation_keyframes_scale__1_o5;
			animation-timing-function: linear;
			animation-fill-mode: both;
		}
		/* 缩放类：缩放倍数1→0.8→1，离场效果 */
		@keyframes _focus_animation_keyframes_scale__1_o8_1{
			0%{ transform: scale(1); }
			50%{ transform: scale(0.8); }
			100%{ transform: scale(1); }
		}
		._focus-animation-class-scale__1_o8_1{
			animation-name: _focus_animation_keyframes_scale__1_o8_1;
			animation-timing-function: linear;
			animation-fill-mode: both;
		}
		
	/* #endif */
</style>